﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using zijian666.WorkScheduler.Abstractions;
using zijian666.WorkScheduler.Defines;

namespace zijian666.WorkScheduler.Defaults;

/// <summary>
/// 工作服务程序。
/// </summary>
public class WorkService : DisposableBase, IWorkService, IDisposable
{
    private readonly TheDoor _pause = new();

    private readonly IServiceProvider _services;
    private readonly IWorkManager _workManager;
    private readonly IWorkDataBuffer _buffer;
    private readonly ITokenBucket _tokenBucket;
    private readonly ILogger? _logger;

    private CancellationTokenSource? _ticket;

    public WorkOptions Options { get; }

    public WorkService(IServiceProvider services)
    {
        _services = services ?? throw new ArgumentNullException(nameof(services));
        Options = this.GetRequiredService<WorkOptions>();
        _workManager = this.GetRequiredService<IWorkManager>();
        _logger = this.GetService<ILogger<WorkService>>(); ;
        _buffer = this.GetService<IWorkDataBuffer>() ?? new WorkDataBuffer();
        Options.ThrowIfInvalid(_workManager is not IWorkHandler);
        _ = GetWorkHandler();

        _ticket = null;

        var expirationPolicy = new ExpirationPolicy(Options.Expires);
        _tokenBucket = new TokenBucket(Options.Threads, expirationPolicy);
    }


    private Task Reloop(CancellationTokenSource? oldticket)
    {
        _logger?.LogDebug("强制刷新数据。");
        oldticket?.Cancel();
        var ticket = new CancellationTokenSource();

        if (Interlocked.CompareExchange(ref _ticket, ticket, oldticket) == oldticket)
        {
            return Loop(ticket);
        }

        _logger?.LogDebug("放弃。");
        return Task.CompletedTask;
    }

    private bool ValidateTicket(CancellationTokenSource ticket)
        => ReferenceEquals(_ticket, ticket);

    private async Task Loop(CancellationTokenSource ticket)
    {
        using var cancellation = CancellationTokenSource.CreateLinkedTokenSource(ticket.Token, Cancellation);
        while (true)
        {
            _logger?.LogDebug("等待刷新数据。");
            // 暂停开关
            await _pause.WaitOpen(cancellation.Token);
            if (!ValidateTicket(ticket))
            {
                _logger?.LogDebug("刷新凭证过期。");
                return;
            }
            _logger?.LogDebug("开始刷新数据。");
            var begin = DateTimeOffset.UtcNow;
            try
            {
                var works = await _workManager.GetPendingWorks(DateTimeOffset.UtcNow.AddMinutes(5), Options.BatchSize, cancellation.Token);
                if (works.Count > 0)
                {
                    // 刷新数据
                    _buffer.RefreshAll(works);
                    _logger?.LogDebug("刷新数据成功。length={length}", works.Count);
                    WakeUp(works.Count);
                }
            }
            catch (Exception e)
                when (!cancellation.IsCancellationRequested)
            {
                _logger?.LogError(e, "刷新数据失败：{message}。", e.Message);
            }
            var delay = Options.RefreshInterval - (DateTimeOffset.UtcNow - begin);
            if (delay > TimeSpan.Zero)
            {
                _logger?.LogDebug("等待下次刷新数据。{delay}", delay);
                await Task.Delay(delay, cancellation.Token);
            }
        }
    }

    private IWorkHandler GetWorkHandler()
    {
        if (Options.WorkHandlerType is null)
        {
            return (IWorkHandler)_workManager;
        }
        return ActivatorUtilities.GetServiceOrCreateInstance(this, Options.WorkHandlerType!) as IWorkHandler
            ?? throw new InvalidOperationException("无法获取 IWorkHandler");
    }



    private async Task<bool> Handler(IToken token)
    {
        var refresh = _ticket;
        while (true)
        {
            token.Activation();
            _logger?.LogDebug("等待获取新的工作数据。");
            var work = await _buffer.GetAndLocking(Guid.NewGuid().ToString("N"), _workManager, token.Cancellation);
            _logger?.LogDebug("工作数据: {work}", work);

            if (work.IsEmpty)
            {
                /* 没有可执行的工作数据, 这里有几种情况
                 * 1. 所有工作数据都在执行中     - 直接申请刷新数据 + 等下次刷新数据22
                 * 2. 已经达到分组最大并发数限制  - 等待某个任务完成再重试 
                 */
                _logger?.LogDebug("等待刷新数据。");
                if (_buffer.PendingCount == 0)
                {
                    _logger?.LogInformation("目前已经全部处理完成，直接刷新数据。");
                    _ = Reloop(refresh);
                }
                return false;
            }

            _logger?.LogDebug("开始执行工作：{id}（{ticket}）。", work.Id, work.Ticket);
            using var context = new WorkContext(_workManager, token, work);
            try
            {
                var handler = GetWorkHandler();
                await context.Report(0, "ready");
                await handler.Execute(context);
                if (!context.IsDisposed)
                {
                    await context.Success("成功", null);
                }
                return true;
            }
            catch (Exception e) when (!context.IsDisposed)
            {
                if (token.IsExpired())
                {
                    await context.Fail(e, "执行超时");
                }
                else
                {
                    await context.Fail(e, "未处理的异常:" + e.Message);
                }
            }
            finally
            {
                context.TryDispose();
                _logger?.LogDebug("工作执行完成：{id}（{ticket}）。", work.Id, work.Ticket);
            }
        }
    }

    /// <inheritdoc />
    public Task Start(CancellationToken token)
    {
        token.Register(Dispose);
        _pause.Open();
        _logger?.LogInformation("开始启动工作服务。");
        return Reloop(_ticket);
    }

    protected override void Dispose(bool disposing)
    {
        _logger?.LogInformation("工作服务已停止。");
        _buffer.TryDispose();
        _pause.TryDispose();
        _tokenBucket.TryDispose();
    }

    /// <inheritdoc />
    public Task Stop(CancellationToken token)
    {
        Interlocked.Exchange(ref _ticket, null);
        _pause.Close();
        _logger?.LogInformation("正在停止工作服务。");
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public void Refresh() => Reloop(_ticket);

    /// <inheritdoc />
    public object? GetService(Type serviceType)
    {
        return _services.GetService(serviceType);
    }

    bool IEquatable<IServiceProvider>.Equals(IServiceProvider? other)
    {
        return ReferenceEquals(_services, other);
    }

    /// <summary>
    /// 唤醒执行器
    /// </summary>
    private void WakeUp(int count)
    {
        _logger?.LogDebug("尝试唤醒 {count} 个执行器。", count);
        for (var i = 0; i < count; i++)
        {
            while (_tokenBucket.Free == 0)
            {
                _logger?.LogTrace("超过执行器上限，中止唤醒操作。");
                return;
            }
            WakeUp();
        }
    }

    /// <summary>
    /// 唤醒执行器，运行一次 <see cref="Handler"/>，如果线程已满则不执行任何操作。
    /// </summary>
    public void WakeUp()
    {
        ThrowIfDisposed();
        var token = _tokenBucket.TryGet();
        if (token is null)
        {
            _logger?.LogDebug("没有可用的令牌，等待下次唤醒。");
            return;
        }
        _logger?.LogDebug("获取调度令牌: {token}。({free}/{total})", token, _tokenBucket.Free, _tokenBucket.Capacity);
        var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, token.Cancellation);
        cts.Token.Register(token.Dispose);
        token.Cancellation.Register(() =>
        {
            _logger?.LogDebug("令牌回收: {token}。({free}/{total})", token, _tokenBucket.Free, _tokenBucket.Capacity);
        });
        _ = Task.Run(async () =>
        {
            _logger?.LogDebug("开始执行: {token}。", token);
            var @continue = true;
            try
            {
                @continue = await Handler(token);
                _logger?.LogDebug("执行成功: {token}。", token);
            }
            catch (Exception e)
            {
                _logger?.LogError(e, "执行异常: {token}。", token);
            }
            finally
            {
                token.TryDispose();
                cts.TryDispose();
                if (@continue)
                {
                    WakeUp();
                }
            }
        }, cts.Token);
    }
}
